; Project name	:	Assembly Library
; Description	:	Functions for displaying formatted strings.
;					** Compressed Strings edition **
;					This is a plug replacement for DisplayFormat.asm,
;					working instead with precompiled and slightly compressed strings.
;
; Strings are compressed in a simple manner:
;	1. The two most common characters, space and null, are removed
;	2. Format specifiers are reduced to a single byte, including length information
;
; Format of bytes in the string are:
;     01 xxxxxx     Character in x plus StringsCompressed_NormalBase
;     10 xxxxxx     Character in x plus StringsCompressed_NormalBase, followed by a null (last character)
;     11 xxxxxx     Character in x plus StringsCompressed_NormalBase, followed by a space
;     00 1 yyyyy    Character/Format in lookup table StringsCopmressed_TranslatesAndFormats
;     00 0 yyyyy    Character/Format in lookup table StringsCompressed_TranslatesAndFormats, followed by a null
;
; StringsCompressed_NormalBase is defined by the compressor, but is usually around 0x40,
; which gives a range of 0x40 to 0x7f, or roughly the upper and lower case letters.
;
; StringsCompressed_TranslatesAndFormats is a lookup table with the first few bytes being translation
; characters, and the last few bytes being format jump offsets from DisplayFormatCompressed_BaseFormatOffset.
; The dividing line is defined by StringsCompressed_FormatsBegin
;
; The assignments of the first two bits above is not by accident.  The translates/format branch is 00
; which is easy to test for.  The '01' for "normal" (no null or space) and '001' for translates/format "normal"
; match, allowing the translates/format codes to be shifted left by 1 and then tested with the same instructions.
;
; It is always possible to say that a null character follows the current character - thus there is
; no way (nor need) to specify a zero character.
;
; Note that this code is optimized for size, not speed.  Since this code is used only during initialization
; and only for the user interface, small performance hits should not be noticed.  It will seem odd to do so
; much "preload", just in case a branch is taken, but that is cheaper (in size) than adding additional branches.
;

; Section containing code
SECTION .text

;--------------------------------------------------------------------
; Format Handlers
;
; Names of format handlers are DisplayFormatCompressed_Format_* where * is
; replaced with the format code after the '%' in the original string,
; with '-' replaced with '_'.
;
;	Parameters:
;		DS:		BDA segment (zero)
;		AX:     Parameter to Format
;		ES:DI:	Ptr to cursor location in video RAM
;	Returns:
;		DI:		Updated offset to video RAM
;	Corrupts registers:
;		AX, BX, CX, DX, SI
;--------------------------------------------------------------------

;
; The following routines do not need any pre or post processing and can be jumped to directly.
; Note that they need to be within 256 bytes of DisplayFormatCompressed_BaseFormatOffset
;
%define DisplayFormatCompressed_Format_c DisplayPrint_CharacterFromAL
%define DisplayFormatCompressed_Format_nl DisplayPrint_Newline_FormatAdjustBP
%define DisplayFormatCompressed_Format_s DisplayFormat_ParseCharacters_FromAX

DisplayFormatCompressed_Format_A:
	mov		[VIDEO_BDA.displayContext+DISPLAY_CONTEXT.bAttribute], al
DisplayFormatCompressed_ret:			; jump target for other routines who need a "ret"
	ret

DisplayFormatCompressed_Format_z:
	xor		bx, bx
	xchg	si, ax
	jmp		short DisplayPrint_NullTerminatedStringFromBXSI

DisplayFormatCompressed_Format_x:
DisplayFormatCompressed_Format_5_x:
	mov		si,16						; hex output, change base to 16
	mov		bx,(04<<8) + 'h'	        ; 4 bytes, with postfix character 'h' to emit
										; (note that the count includes the 'h')
	jmp		DisplayFormatCompressed_Format_u

DisplayFormatCompressed_Format_2_I:
	mov		si,g_szDashForZero			; preload dash string in case we jump
	test	ax,ax						; if parameter equals zero, emit dash string instead
	jz		DisplayFormat_ParseCharacters
	; fall through

DisplayFormatCompressed_Format_2_u:
	mov		bh,2						; only two characters (instead of the default 5)
	; fall through

DisplayFormatCompressed_Format_u:
DisplayFormatCompressed_Format_5_u:
	push	bx							; push postfix character - either a zero (default) or a 'h'
	mov		bl,bh						; preserve character count for .PrintLoop

.DivLoop:
	xor		dx, dx						; Zero DX for division
	div		si							; DX:AX / 10 => AX=quot, DX=rem
 	push	dx							; Push digit

	dec		bh
	jnz		.DivLoop

.PrintLoop:
	pop		ax							; Pop digit, postfix character on last iteration

	dec		bl							; on second to last iteration, emit digit whether it is zero or not
	jz		.PrintDigit

	js		short DisplayPrint_CharacterFromAL	; on last iteration, emit postfix character
												; if it is zero, DisplayPrint_CharacterFromAL will not emit

	or		bh, al						; skip leading zeros, bh keeps track if we have emitted anything non-zero
	jnz		.PrintDigit					; note that bh starts at zero, from the loop above

	test	ch,2						; are we padding with leading spaces?
	jnz		.PrintLoop					; test the even/odd of the format byte in the string

	mov		al, 89h						; emit space

.PrintDigit:
	add		al, 90h						; Convert binary digit in AL to ASCII hex digit (0 - 9 or A - F)
	daa
	adc		al, 40h
	daa

	call	DisplayPrint_CharacterFromAL

	jmp		.PrintLoop


;--------------------------------------------------------------------
; DisplayFormat_ParseCharacters
;	Parameters:
;		DS:		BDA segment (zero)
;		SS:BP:	Pointer to first format parameter (-=2 updates to next parameter)
;		CS:SI:	Pointer to string to format
;		ES:DI:	Ptr to cursor location in video RAM
;	Returns:
;		CS:SI:	Ptr to end of format string (ptr to one past NULL)
;		DI:		Updated offset to video RAM
;	Corrupts registers:
;		AX, BX, CX, DX, BP
;--------------------------------------------------------------------

DisplayFormatCompressed_BaseFormatOffset:

DisplayFormat_ParseCharacters_FromAX:
	mov		si,ax
	; fall through to DisplayFormat_ParseCharacters

ALIGN JUMP_ALIGN
DisplayFormat_ParseCharacters:
;
; This routine is used to output all strings from the ROM.  The strings in ROMVARS are not compressed,
; and must be handled differently.
;
	cmp		si,byte 07fh		; well within the boundaries of ROMVARS_size
	mov		bx,cs				; preload bx with cs in case we take the following jump
	jb		short DisplayPrint_NullTerminatedStringFromBXSI

.decode:
	cs lodsb					; load next byte of the string

	mov		ch,al				; save a copy for later processing of high order bits

	test	al,0c0h				; check for translation/format character
	jz		DisplayFormatCompressed_TranslatesAndFormats

	and		al,03fh								; "Normal" character, mask off high order bits
	add		al,StringsCompressed_NormalBase		; and add character offset (usually around 0x40)

.output:
	call 	DisplayPrint_CharacterFromAL

.process_after_output:
	shl		ch,1								; check high order bits for end of string or space
	jns		short DisplayFormatCompressed_ret
	jnc		.decode
	mov		al,' '
	call	DisplayPrint_CharacterFromAL
	jmp		.decode


ALIGN JUMP_ALIGN
DisplayFormatCompressed_TranslatesAndFormats:
;
; This routine is here (above DisplayFormat_ParseCharacters) to reduce the amount of code between
; DisplayFormatCompressed_BaseFormatOffset and jump targets (must fit in 256 bytes)
;
	shl		ch,1				; setup ch for later testing of null in .process_after_output
	and		ax,0001fh			; also clears AH for addition with BX and DX below

	mov		bx,StringsCompressed_TranslatesAndFormats	; calculate offset of translation/formats offset byte
	add		bx,ax

	cmp		al,StringsCompressed_FormatsBegin			; determine if this is a translation or a format

	mov		al,[cs:bx]									; fetch translation/formats byte

	jb		DisplayFormat_ParseCharacters.output		; check if this a translation or a format
														; if it is translation, output and postprocess for eos
														; note that the flags for this conditional jump were
														; set with the cmp al,StringsCompressed_FormatsBegin

	mov		dx,DisplayFormatCompressed_BaseFormatOffset   ; calculate address to jump to for format handler
	sub		dx,ax

	mov		ax,[bp]				; preload ax with parameter
	dec		bp					; if no parameter is needed (format 'nl' for example),
	dec		bp					; the format handler can reincrement bp

	mov		bx,0500h			; preload bh with 5 decimal places for numeric output
								; bl is zero, indicating not to output a 'h' (default base 10)

	push	si					; preserve si and cx, in the case of outputing a string
	push	cx

	mov		si,10				; preload si with 10 for numeric output (default base 10)

	call	dx					; call the format routine

	pop		cx					; restore cx and si
	pop		si

	jmp		DisplayFormat_ParseCharacters.process_after_output	; continue postprocessing, check for end of string

